

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#define LEFT "["

#define RIGHT "]"

#define LINE_SIZE 1024  // 命令行缓冲区大小

#define ARGV_SIZE 32

#define DELIM " \t"

#define EXIT_CODE 33

// 获取环境变量为标识符
/*
    [ USER@HOSTNAME PWD ]label
*/
int lastcode = 0;

int quit = 0;

char commandline[LINE_SIZE];

char* argv[ARGV_SIZE];

char pwd[LINE_SIZE];

char currvariables[LINE_SIZE];

const char* getUsername() { return getenv("USER"); }

const char* getHostname() { return getenv("HOSTNAME"); }

void getpwd() {
  // return getenv("PWD");
  getcwd(pwd, sizeof(pwd));
}

void interact(char* cline, size_t size) {
  getpwd();
  printf(LEFT "%s@%s %s" RIGHT "# ", getUsername(), getHostname(), pwd);

  char* s = fgets(cline, size, stdin);  // 从键盘获取

  size_t len = strlen(cline);

  if (len > 1) cline[len - 1] = '\0';

  assert(s);

  (void)s;
}

int splitstring(char* cline, char** argv) {
  int i = 0;
  argv[i++] = strtok(cline, DELIM);
  while ((argv[i++] = strtok(NULL, DELIM))) {
    ;
  }
  return i - 1;
}

void normalExecute(char** _argv) {
  //
  pid_t id = fork();
  if (id < 0) {
    //
    perror("fork error");
    return;
  } else if (id == 0) {
    // 子进程执行程序
    execvp(_argv[0], _argv);
    exit(EXIT_CODE);  // 进程替换失败
  } else {
    // 父进程等待子进程
    int status = 0;
    int ret = waitpid(id, &status, 0);  // 等待成功返回pid 失败则返回-1
    if (ret == id) {
      lastcode = WEXITSTATUS(status);
    }
  }
}

int buildCommand(int _argc, char** _argv) {
  getpwd();
  if (_argc == 2 && (strcmp(_argv[0], "cd") == 0)) {
    int dirret = chdir(_argv[1]);
    if (dirret != 0) {  // 说明chdir调用失败
      perror("chdir error\n");
    } else {
      sprintf(getenv("PWD"), "%s", pwd);
    }
    return 1;
  }

  else if (_argc == 2 && strcmp(_argv[0], "export") == 0) {
    // 两种方法都适用
    // ①
    /* char* _name = strtok(_argv[1],"=");
    char* _value = strtok(NULL, "");
    if(_name&&_value){
      setenv(_name, _value, 1);

      //使用putenv的话只是单纯的将字符串的指针写在了环境变量表之中
      //而argv当中的数据是不停变化的
      // 故若是使用putenv的话由于argv的内容在不停变化
      //则会使这个环境变量表的指针指向一个无效的位置
      //故要么将这个环境变量表的指针单独存放一个位置 要么就使用setenv
    }*/

    // ②
    strcpy(currvariables, _argv[1]);
    putenv(currvariables);
    return 1;
  }

  else if (_argc == 2 && strcmp(_argv[0], "echo") == 0) {
    if (strcmp(_argv[1], "$?") == 0) {
      printf("%d\n", lastcode);
      lastcode = 0;
    } else if (_argv[1][0] == '$') {
      // 说明需要打印的是环境变量
      char* val = getenv(_argv[1] + 1);
      if (val) printf("%s\n", val);
    } else {
      // 说明不属于内建命令可以直接打印
      printf("%s\n", _argv[1]);
    }
    return 1;
  }

  else if (strcmp(_argv[0], "ls") == 0) {
    _argv[_argc++] = "--color";
    _argv[_argc] = NULL;
  }
  return 0;
}

int main() {
  while (!quit) {
    // 获取命令行信息
    interact(commandline, sizeof(commandline));

    // debug 打印测试输入信息是否正确
    printf("echo: %s\n", commandline);

    // 分割命令行信息
    int argc = splitstring(commandline, argv);  // 分割字符串
    if (argc == 0) continue;
    /*
        如果未进行判断的话 将会发生下列问题:
        当只输入分隔符的情况下数据将不被分割
        对应的argv当中则只有NULL
          由于没有中断此次循环将会继续向下执行
          而在接下来的执行当中 程序将期望存在一个有效的命令
          然而argv[0]为NULL 将会进行报错
    */

    //  //用于debug 对指针数组进行打印
    // for (int i = 0; argv[i] != NULL; ++i) {
    //   printf("%s\n", argv[i]);
    // }

    // 内建命令
    int flag = buildCommand(argc, argv);

    // // 分割命令后将对命令进行处理
    if (!flag) normalExecute(argv);
  }
  return 0;
}
